import pako from 'pako'
import CryptoJS from 'crypto-js'

function u8arrayToWordArray(u8arr: Uint8Array) {
  const len = u8arr.length
  const wordArray: any[] = []
  for (let i = 0; i < len; i++) {
    wordArray[i >>> 2] |= (u8arr[i] & 0xFF) << (24 - (i % 4) * 8)
  }
  return CryptoJS.lib.WordArray.create(wordArray, len)
}

function wordArrayToU8array(wordArray: CryptoJS.lib.WordArray) {
  const words = wordArray.words
  const sigBytes = wordArray.sigBytes
  const u8arr = new Uint8Array(sigBytes)
  for (let i = 0; i < sigBytes; i++) {
    u8arr[i] = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xFF
  }
  return u8arr
}

function getAppKey(appKey: string, parse = true) {
  return parse ? CryptoJS.enc.Utf8.parse(appKey) : appKey
}

// 加密方法
function encrypt(data: any, appKey: string) {
  if (typeof data !== 'string') {
    data = JSON.stringify(data)
  }

  return aesEncrypt(gzip(data), appKey)
}

// aes加密
function aesEncrypt(zipData: Uint8Array, appKey: string) {
  let data

  // Uint8Array的数据转成WordArray的数据
  if (typeof zipData !== 'string') {
    data = u8arrayToWordArray(zipData)
  }
  const key = getAppKey(appKey) as CryptoJS.lib.WordArray
  const encrypt = CryptoJS.AES.encrypt(data, key, {
    iv: key,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  })
  return encrypt.toString()
}

// 解密方法
function decrypt(data: string, appKey: string) {
  return ungzip(aesDecrypt(data, appKey))
}

// aes解密
function aesDecrypt(data: string, appKey: string) {
  const key = getAppKey(appKey) as CryptoJS.lib.WordArray
  const decrypt = CryptoJS.AES.decrypt(data, key, {
    iv: key,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  })
  // 转成Unit8Array方便Gzip解压
  return wordArrayToU8array(decrypt)
}

// gzip压缩
function gzip(str: string) {
  return pako.gzip(str)
}

/**
 * 图片解密，后端未压缩，所以不需要解压
 */
function decryptImg(data: string, key: string) {
  const commonKey = getAppKey(key) as CryptoJS.lib.WordArray
  const decrypt = CryptoJS.AES.decrypt(data, commonKey, {
    iv: commonKey,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  })
  return decrypt.toString(CryptoJS.enc.Utf8).toString()
}

// gzip解压
function ungzip(data: Uint8Array | ArrayBuffer) {
  const wordArray = u8arrayToWordArray(pako.inflate(data))
  return CryptoJS.enc.Utf8.stringify(wordArray)
}

function md5(data: string | CryptoJS.lib.WordArray) {
  return CryptoJS.MD5(data).toString()
}

function createSign(data: string, nonce: number | string, timestamp: number | string, appKey: string) {
  const sign = data + appKey + nonce + timestamp
  return md5(md5(sign))
}

function createNonce() {
  return Math.round(Math.random() * 2 ** 63)
}

function createTimestamp() {
  return new Date().getTime()
}

export const cryptoUtils = {
  md5,
  encrypt,
  decrypt,
  decryptImg,
  createSign,
  createNonce,
  createTimestamp
}
